﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using System.Windows.Media.Imaging;
using Groovy_IM.Assets.EnumStructs;

namespace Groovy_IM.Protocols.AIM
{
    public class OSCAR
    {
        public delegate void BOSErrorCode2(string ErrorCode);
        public delegate void BuddyChatUpdate2(string Chat, int Exchange, int Type, string Username, bool EnteredRoom);
        public delegate void BuddyGroup2(string GroupName, int GroupID);
        public delegate void BuddyItem2(string BuddyName, int GroupID, int BuddyID);
        public delegate void BuddyRateClass2(double MsgCode, int RateClass, double WindowSize, double ClearLevel, double AlertLevel, double LimitLevel, double DisconnectLevel, double CurrentLevel, double MaxLevel, double LastTime, double CurrentStatus);
        public delegate void BuddyStatusUpdate2(string BuddyName, int Status, string StatusMessage);
        public delegate void IncomingChatInvite2(string Screenname, string InviteMessage, string Chat, string Exchange, string Type, string Instance, string Details);
        public delegate void IncomingChatMessage2(string Screenname, string Message);
        public delegate void IncomingICBM2(string Screenname, string Message);
        public delegate void IncomingUserIcon2(string Screenname, BitmapImage Avatar);
        public delegate void IncomingUserInformation2(string Screenname, string Profile, int Status);
        public delegate void IncomingWarn2(string Screenname, int WarningLevel);
        public delegate void JoinChatError2(string Chat, string Error);
        public delegate void JoinedChat2(string Chat, int Exchange, int Type);
        public delegate void LoggedIn2();
        public delegate void LoggedOut2();
        public delegate void LoggingIn2(double CurrentStatus);
        public delegate void LoginError2(string Error);
        public delegate void MigrationCompleted2(bool Canceled);
        public delegate void MigrationStarted2();

        public event BOSErrorCode2 BosErrorCode;
        public event BuddyChatUpdate2 BuddyChatUpdate;
        public event BuddyGroup2 BuddyGroup;
        public event BuddyItem2 BuddyItem;
        public event BuddyRateClass2 BuddyRateClass;
        public event BuddyStatusUpdate2 BuddyStatusUpdate;
        public event IncomingChatInvite2 IncomingChatInvite;
        public event IncomingChatMessage2 IncomingChatMessage;
        public event IncomingICBM2 IncomingICBM;
        public event IncomingUserIcon2 IncomingUserIcon;
        public event IncomingUserInformation2 IncomingUserInformation;
        public event IncomingWarn2 IncomingWarn;
        public event JoinChatError2 JoinChatError;
        public event JoinedChat2 JoinedChat;
        public event LoggedIn2 LoggedIn;
        public event LoggedOut2 LoggedOut;
        public event LoggingIn2 LoggingIn;
        public event LoginError2 LoginError;
        public event MigrationCompleted2 MigrationCompleted;
        public event MigrationStarted2 MigrationStarted;

        private PacketBuilder m_Packets = new PacketBuilder();

        private Sock m_Auth, m_BOS, m_ChatNav, m_BART;

        private Sock[] m_Chat = new Sock[10];

        private byte[] m_BOS_Cookie, m_BART_Cookie;

        private Timer m_BOS_Timer, m_ChatNav_Timer;

        private Timer[] m_Chat_Timer = new Timer[10];

        private int[] m_Chat_Connected;

        private Dictionary<int, byte[]> m_Chat_Cookies = new Dictionary<int, byte[]>();

        private Dictionary<int, string> m_Chat_Info = new Dictionary<int, string>();

        private Queue<byte> m_Services = new Queue<byte>(), m_BART_Serv = new Queue<byte>();

        private int m_Port, m_BuddylistID, m_BuddylistID2;

        private string m_Screenname, m_Password, m_Status;

        private bool m_Mobile, m_Invisible, m_BART_Connected;

        public void Login(string Screenname, string Password, string Server, int Port, bool MobileLogin, bool InvisibleLogin, string Status)
        {
            if (LoggingIn != null)
                LoggingIn(0);

            m_Screenname = Screenname;

            m_Password = Password;

            m_Port = Port;

            m_Mobile = MobileLogin;

            m_Invisible = InvisibleLogin;

            m_Status = Status;

            m_Auth = new Sock();

            m_Auth.OnConnect += new Sock.Connected(m_Auth_OnConnect);

            m_Auth.DataArrival += new Sock.OnArrival(m_Auth_DataArrival);

            m_Auth.beginConnect(Server, Port);
        }

        public void Logoff()
        {
            if (m_BOS == null || m_BOS.State == 0)
                return;

            m_BOS.Close();

            m_Queue.Dispose();

            Packets.Clear();

            m_BOS_Timer.Dispose();

            if (LoggedOut != null)
                LoggedOut();
        }

        public void SetInvisible(bool Invisible)
        {
            if (m_BOS == null || m_BOS.State == 0)
                return;

            Send_BOS_Packet(m_Packets.SetNickStatus(true, Convert.ToInt32(Invisible), false, null));
        }

        public void SetStatus(string Status)
        {
            if (m_BOS == null || m_BOS.State == 0)
                return;

            Send_BOS_Packet(m_Packets.SetNickStatus(false, 0, true, Status));
        }

        /*Sockets*/
        void m_Auth_DataArrival(FLAPSocket packet)
        {
            handleAuth(packet);
        }

        void m_Auth_OnConnect()
        {
            m_Auth.SendData(m_Packets.ConnectPacket());
        }

        void m_BOS_OnConnect()
        {
            m_BOS.SendData(m_Packets.FlapCookie(m_BOS_Cookie));
        }

        void m_BART_DataArrival(FLAPSocket packet)
        {
            switch (packet.Channel)
            {
                case FLAPChannel.SNAC:
                    handleBART(packet);
                    break;
                case FLAPChannel.Disconnect:
                    /*m_BART_Connected = false;
                    Send_BOS_Packet(m_Packets.RequestService(92, 16));*/
                    break;
            }
        }

        void m_BART_OnConnect()
        {
            m_BART.SendData(m_Packets.FlapCookie(m_BART_Cookie));
        }

        void m_BOS_DataArrival(FLAPSocket packet)
        {
            switch (packet.Channel)
            {
                case FLAPChannel.SNAC:
                    handleBOS(packet);
                    break;
                case FLAPChannel.Disconnect:
                    Logoff();
                    return;
            }
        }

        void m_BOS_OnClose()
        {

        }
        /*End Sockets */

        /*Handling Functions*/
        private void handleAuth(FLAPSocket packet)
        {
            switch (packet.Channel)
            {
                case FLAPChannel.Connect:
                    m_Auth.SendData(m_Packets.RequestChallenge(m_Screenname));
                    break;
                case FLAPChannel.SNAC:
                    switch (packet.Data[3])
                    {
                        case 3:
                            handleConnection(packet);
                            break;
                        case 7:
                            byte[] challenge = new byte[Tools.GetWord(packet.Data, 10)];

                            Buffer.BlockCopy(packet.Data, 12, challenge, 0, challenge.Length);

                            m_Auth.SendData(m_Packets.Login(m_Screenname, Encoding.UTF8.GetBytes(m_Password), challenge, m_Mobile));
                            break;
                    }
                    break;
                case FLAPChannel.Disconnect:
                    m_Auth.Close();
                    break;
            }
        }

        private void handleBART(FLAPSocket packet)
        {
            ushort pFamily = Tools.GetWord(packet);

            ushort pSubFamily = Tools.GetWord(packet);

            switch (pFamily)
            {
                case 1:
                    switch (pSubFamily)
                    {
                        case 3:
                            int packet_Offset = 11; //Set Packet Offset To 11 (So we can grab the Services the Server don't mind us using.)

                            while (packet_Offset < packet.Data.Length)
                            {
                                m_BART_Serv.Enqueue(packet.Data[packet_Offset]); //Add service to queue.

                                packet_Offset += 2; //Add 2 to the packet to get next service.
                            }

                            m_BART.SendData(m_Packets.ServiceVersions(m_BART_Serv)); //Send our supported version list. (Server may bring us different stuff back).                            
                            break;
                        case 24:
                            m_BART.SendData(m_Packets.ServiceReady(m_BART_Serv));
                            m_BART_Connected = true;
                            break;
                    }
                    break;
                case 16:
                    switch (pSubFamily)
                    {
                        case 5:
                            handleBuddyIcon(packet);
                            break;
                        case 7:
                            handleBuddyIcon(packet);
                            break;
                    }
                    break;
            }
        }

        private void handleBOS(FLAPSocket packet)
        {
            ushort pFamily = Tools.GetWord(packet);

            ushort pSubFamily = Tools.GetWord(packet);

            switch (pFamily)
            {
                case 1:
                    switch (pSubFamily)
                    {
                        case 1: //Error -- Many different types of errors here..most notable are Join Chat errors.
                            /*int errorCode = Tools.GetWord(packet.Data, 16);

                            switch (errorCode)
                            {
                                case 1:
                                    if (BosErrorCode != null)
                                        BosErrorCode("An error has occured. Please re-connect.");
                                    break;
                                case 2:
                                    if (BosErrorCode != null)
                                        BosErrorCode("You have exceeded the rate limit. Please re-connect.");
                                    break;
                                case 2:
                                    if (BosErrorCode != null)
                                        BosErrorCode("You have exceeded the rate limit. Please re-connect.");
                                    break;
                            }*/
                            break;
                        case 3: //Server Supported SNAC list -- {Kuzco} We should create a queue and add services to it in order to make sure we will make good use of the service.
                            int packet_Offset = 11; //Set Packet Offset To 11 (So we can grab the Services the Server don't mind us using.)

                            while (packet_Offset < packet.Data.Length)
                            {
                                m_Services.Enqueue(packet.Data[packet_Offset]); //Add service to queue.

                                packet_Offset += 2; //Add 2 to the packet to get next service.
                            }

                            m_BOS.SendData(m_Packets.ServiceVersions(m_Services)); //Send our supported version list. (Server may bring us different stuff back).                            
                            break;
                        case 5: //Redirection (Chats, etc..)
                            handleRedirection(packet);
                            break;
                        case 7: //Rate Limits Information
                            handleRateClasses(packet);

                            Send_BOS_Packet(m_Packets.RateAck());

                            Send_BOS_Packet(m_Packets.RequestOwnInformation());

                            Send_BOS_Packet(m_Packets.RequestServiceParameters());

                            Send_BOS_Packet(m_Packets.RequestContactList(true));

                            Send_BOS_Packet(m_Packets.RequestLocationRights());

                            Send_BOS_Packet(m_Packets.RequestBuddylistRights());

                            Send_BOS_Packet(m_Packets.RequestICBMParamRights());

                            Send_BOS_Packet(m_Packets.RequestBuddylistPrivacyRights());
                            break;
                        case 10: //Rate Information Changed
                            handleRateChange(packet);
                            break;
                        case 11: //Server Pause Information
                            Send_BOS_Packet(m_Packets.RequestMigration(m_Services));
                            m_SendPackets = false;

                            if (MigrationStarted != null)
                                MigrationStarted();
                            break;
                        case 13: //Server Resume
                            m_SendPackets = true;

                            if (MigrationCompleted != null)
                                MigrationCompleted(true);
                            break;
                        case 16: //Evil Notification
                            int WarningLevel = Tools.GetWord(packet.Data, 10);

                            string Screenname = "Anonymous";

                            if (packet.Data.Length > 14)
                            {
                                int ScreennameLength = Tools.GetWord(packet.Data, 12);

                                Screenname = Encoding.UTF8.GetString(packet.Data, 14, ScreennameLength);
                            }

                            if (IncomingWarn != null)
                                IncomingWarn(Screenname, WarningLevel);
                            break;
                        case 18: //Migration Notice & Info
                            m_SendPackets = true;

                            if (MigrationCompleted != null)
                                MigrationCompleted(false);
                            break;
                        case 19: //Message Of The Day (MOTD) -- {Kuzco} SKIP! {0.3.0.0}
                            break;
                        case 21: //Well Known URLs -- {Kuzco} SKIP! {0.3.0.0}
                            break;
                        case 22: //No Operation (NOP) -- {Kuzco} Deprecated -- Not used by AOL {0.3.0.0}
                            break;
                        case 24: //Server Versions Information -- {Kuzco} I really don't see the need to filter the data from this as of version (0.3.0.0).
                            m_BOS.SendData(m_Packets.RequestRateLimits());

                            if (LoggingIn != null)
                                LoggingIn(50);
                            break;
                        case 31: //Client Verification Request -- {Kuzco} I have never seen this or been affected by it.
                            break;
                        case 33: //Cleints Extended Status -- {Kuzco} No need to parse OWN Buddy Icon as of this time.
                            break;
                    }
                    break;
                case 2:
                    switch (pSubFamily)
                    {
                        case 3: //Location Rights -- {Kuzco} I don't want to filter the data from this as of version (0.3.0.0) -- {Kuzco} This supports "MAX" Profile Length, and Capabilities.
                            Send_BOS_Packet(m_Packets.SetMyInformation(false, "", false, "", true, AIMClasses.String_To_Bytes("09461341-4C7F-11D1-8222-444553540000, 09461342-4C7F-11D1-8222-444553540000, 09461343-4C7F-11D1-8222-444553540000, 09461345-4C7F-11D1-8222-444553540000, 09461346-4C7F-11D1-8222-444553540000, 09461348-4C7F-11D1-8222-444553540000, 0946134D-4C7F-11D1-8222-444553540000, 0946134E-4C7F-11D1-8222-444553540000, 748F2420-6287-11D1-8222-444553540000, 09460000-4C7F-11D1-8222-444553540000, 09460101-4C7F-11D1-8222-444553540000, 09460102-4C7F-11D1-8222-444553540000, 09460103-4C7F-11D1-8222-444553540000, 09460104-4C7F-11D1-8222-444553540000, 0946010A-4C7F-11D1-8222-444553540000, 094601FF-4C7F-11D1-8222-444553540000")));
                            break;
                    }
                    break;
                case 3:
                    switch (pSubFamily)
                    {
                        case 3: //Buddylist Rights Reply -- {Kuzco} I don't see the point in filtering this as of version (0.3.0.0) -- {Kuzco} This supports Max Contacts, Notifications, Watcher List Entries.
                            break;
                        case 11: //User Came Online!  -- {Kuzco} He must like cumming no matter when.
                            handleBuddy(packet, true);
                            break;
                        case 12: //User Came Offline! -- {Kuzco} He must like cumming no matter when.
                            handleBuddy(packet, false);
                            break;
                    }
                    break;
                case 4:
                    switch (pSubFamily)
                    {
                        case 5: //ICBM Parameters -- {Kuzco} Will parse at a future time...not version 0.3.0.0
                            Send_BOS_Packet(m_Packets.SetMyICBM());
                            Send_BOS_Packet(m_Packets.SetMyDCInfo(0));
                            Send_BOS_Packet(m_Packets.SetMyDCInfo(1));
                            Send_BOS_Packet(m_Packets.SetMyDCInfo(2));
                            break;
                    }
                    break;
                case 9:
                    switch (pSubFamily)
                    {
                        case 3:
                            break;
                    }
                    break;
                case 19:
                    switch (pSubFamily)
                    {
                        case 3:
                            break;
                        case 6:
                            handleBuddylist(packet);

                            if (LoggingIn != null)
                                LoggingIn(90);

                            Send_BOS_Packet(m_Packets.ActivateSSI());
                            Send_BOS_Packet(m_Packets.ServiceReady(m_Services));

                            if (LoggedIn != null)
                                LoggedIn();

                            SetInvisible(m_Invisible);

                            SetStatus(m_Status);
                            break;
                    }
                    break;
            }
        }

        private void handleBuddy(FLAPSocket packet, bool Online)
        {
            UserInfo ui = new UserInfo();

            byte snLen = packet.Data[10];

            if (snLen == 0)
                return;

            int Packet_Offset = 11;

            ui.ScreenName = Encoding.UTF8.GetString(packet.Data, Packet_Offset, snLen);

            Packet_Offset += snLen;

            ui.WarningLevel = Tools.GetWord(packet.Data, Packet_Offset);

            Packet_Offset += 2;

            ui.Icon = null;

            ushort TLVs = Tools.GetWord(packet.Data, Packet_Offset);

            Packet_Offset += 2;

            if (Online == true)
                ui.AvailableMessage = "Online";
            else
                ui.AvailableMessage = "Offline";

            for (int i = 0; i < TLVs; i++)
            {
                ushort Family = Tools.GetWord(packet.Data, Packet_Offset);

                Packet_Offset += 2;

                ushort TLVLength = Tools.GetWord(packet.Data, Packet_Offset);

                Packet_Offset += 2;

                switch (Family)
                {
                    case 6:
                        ui.UserStatus = Tools.GetDword(packet.Data, Packet_Offset);
                        Packet_Offset += TLVLength;
                        break;
                    case 29:
                        int end = Packet_Offset + TLVLength;

                        while (Packet_Offset < end)
                        {
                            ushort type = Tools.GetWord(packet.Data, Packet_Offset);

                            Packet_Offset += 2;

                            byte number = packet.Data[Packet_Offset];

                            Packet_Offset++;

                            byte length = packet.Data[Packet_Offset];

                            Packet_Offset++;

                            switch (type)
                            {
                                case 0:
                                    Packet_Offset += length;
                                    break;
                                case 1:
                                    if (length > 0)
                                    {
                                        ui.Icon = new IconInfo();

                                        ui.Icon.Flags = number;

                                        ui.Icon.Hash = new byte[length];

                                        Buffer.BlockCopy(packet.Data, Packet_Offset, ui.Icon.Hash, 0, length);

                                        Packet_Offset += length;
                                    }
                                    else
                                        Packet_Offset += length;
                                    break;
                                case 2:
                                    if (length > 4)
                                    {
                                        ushort a_length = Tools.GetWord(packet.Data, Packet_Offset);

                                        Packet_Offset += 2;

                                        ui.AvailableMessage = Encoding.UTF8.GetString(packet.Data, Packet_Offset, a_length);

                                        Packet_Offset += a_length;

                                        ushort Enc = Tools.GetWord(packet.Data, Packet_Offset);

                                        Packet_Offset += 4;

                                        if (Enc == 0x0001)
                                        {
                                            ushort AE_Length = Tools.GetWord(packet.Data, Packet_Offset);

                                            Packet_Offset += 2;

                                            ui.AvailableMessageEncoding = Encoding.UTF8.GetString(packet.Data, Packet_Offset, AE_Length);

                                            Packet_Offset += AE_Length;
                                        }
                                        else
                                            ui.AvailableMessageEncoding = "";
                                    }
                                    else
                                        Packet_Offset += length;
                                    break;
                                default:
                                    Packet_Offset += length;
                                    break;
                            }
                        }
                        break;
                    default:
                        Packet_Offset += TLVLength;
                        break;
                }
            }

            if (BuddyStatusUpdate != null)
                BuddyStatusUpdate(ui.ScreenName, (int)ui.UserStatus, ui.AvailableMessage);
        }

        private void handleBuddylist(FLAPSocket packet)
        {
            int Packet_Offset = 11, m_Count = 0;

            int m_BuddylistVersion = packet.Data[10];

            int m_BuddylistItems = Tools.GetWord(packet.Data, Packet_Offset);

            Packet_Offset += 2;

            while (m_Count < m_BuddylistItems)
            {
                string Item;

                int ItemLength = 0, ItemType = 0, ItemID = 0, GroupID = 0, AttributeLength = 0;

                ItemLength = Tools.GetWord(packet.Data, Packet_Offset);

                Packet_Offset += 2;

                Item = Encoding.UTF8.GetString(packet.Data, Packet_Offset, ItemLength);

                Packet_Offset += ItemLength;

                GroupID = Tools.GetWord(packet.Data, Packet_Offset);

                Packet_Offset += 2;

                ItemID = Tools.GetWord(packet.Data, Packet_Offset);

                Packet_Offset += 2;

                ItemType = Tools.GetWord(packet.Data, Packet_Offset);

                Packet_Offset += 2;

                AttributeLength = Tools.GetWord(packet.Data, Packet_Offset);

                Packet_Offset += 2;

                switch (ItemType)
                {
                    case 0:
                        if (Item.Length > 0 && BuddyItem != null)
                            BuddyItem(Item, GroupID, ItemID);
                        break;
                    case 1:
                        if (Item.Length > 0 && BuddyGroup != null)
                            BuddyGroup(Item, GroupID);
                        break;
                    case 2:
                        //Allow List Settings --- Version 0.3.0.0 does not need this.
                        break;
                    case 3:
                        //Block List Settings --- Version 0.3.0.0 does not need this.
                        break;
                    case 4:
                        m_BuddylistID = packet.Data[Packet_Offset - 6];

                        m_BuddylistID2 = packet.Data[Packet_Offset - 5];

                        //Permit / Deny Settings --- Version 0.3.0.0 does not need this.
                        break;
                    case 5:
                        //Presence Information --- Version 0.3.0.0 does not need this.
                        break;
                    case 9:
                        //??? --- Version 0.3.0.0 does not need this.
                        break;
                    case 0x0e:
                        //Ignore List Settings --- Version 0.3.0.0 does not need this.
                        break;
                    case 0x0f:
                        //Last Update Settings --- Version 0.3.0.0 does not need this.
                        break;
                    case 0x10: //Non ICQ Contact --- Version 0.3.0.0 does not need this.
                        break;
                    case 0x13: //Roster Import Time --- Version 0.3.0.0 does not need this.
                        break;
                    case 0x14: //Own Icon --- Version 0.3.0.0 does not need this.
                        break;
                }

                Packet_Offset += AttributeLength;

                m_Count++;
            }
        }

        private void handleConnection(FLAPSocket packet)
        {
            string BOS_Server = "";

            int BOS_Port = m_Port, packet_Offset = 10, word_Length = 0, word_Type = 0, errorCode = 0;

            while (packet_Offset < packet.Data.Length)
            {
                word_Type = Tools.GetWord(packet.Data, packet_Offset);

                packet_Offset += 2;

                word_Length = Tools.GetWord(packet.Data, packet_Offset);

                packet_Offset += 2;

                switch (word_Type)
                {
                    case 1:
                        string formSN = Encoding.UTF8.GetString(packet.Data, packet_Offset, word_Length);

                        if (formSN.Replace(" ", "").ToLower() != m_Screenname)
                        {
                            m_Screenname = formSN.Replace(" ", "").ToLower();
                        }
                        break;
                    case 5:
                        string BOSTemp = Encoding.UTF8.GetString(packet.Data, packet_Offset, word_Length);

                        string[] BOSSplit = BOSTemp.Split(":".ToCharArray());

                        BOS_Server = BOSSplit[0];

                        BOS_Port = Convert.ToInt32(BOSSplit[1]);
                        break;
                    case 6:
                        m_BOS_Cookie = new byte[word_Length];

                        Buffer.BlockCopy(packet.Data, packet_Offset, m_BOS_Cookie, 0, word_Length);
                        break;
                    case 8:
                        errorCode = Tools.GetWord(packet.Data, packet_Offset);
                        break;
                }

                packet_Offset += word_Length;
            }

            if (errorCode != 0)
            {
                if (LoginError != null)
                    LoginError(AIMClasses.errorText(errorCode));

                return;
            }

            if (LoggingIn != null)
                LoggingIn(30);

            m_Auth.Close();

            m_BOS = new Sock();

            m_BOS.OnConnect += new Sock.Connected(m_BOS_OnConnect);

            m_BOS.DataArrival += new Sock.OnArrival(m_BOS_DataArrival);

            m_BOS.OnClose += new Sock.Closed(m_BOS_OnClose);

            m_BOS.beginConnect(BOS_Server, BOS_Port);
        }

        private void handleBuddyIcon(FLAPSocket packet)
        { //{Kuzco} We must parse this...
            int snLen = packet.Data[10];

            if (snLen < 1)
                return;

            int Packet_Offset = 11;

            string Screenname = Encoding.UTF8.GetString(packet.Data, Packet_Offset, snLen);

            Packet_Offset += snLen + 2;

            int AvatarFlag = packet.Data[Packet_Offset];

            Packet_Offset++;

            int AvatarHashLen = packet.Data[Packet_Offset];

            Packet_Offset += AvatarHashLen + 1;

            int AvatarLen = Tools.GetWord(packet.Data, Packet_Offset);

            Packet_Offset += 2;

            byte[] Avatar = new byte[AvatarLen];

            Buffer.BlockCopy(packet.Data, Packet_Offset, Avatar, 0, AvatarLen);

            if (IncomingUserIcon != null && Avatar.Length > 4)
                IncomingUserIcon(Screenname, byteArrayToImage(Avatar));
        }

        private BitmapImage byteArrayToImage(byte[] byteArrayIn)
        {
            BitmapImage img = new BitmapImage();

            if (byteArrayIn != null)
            {
                using (MemoryStream stream = new MemoryStream(byteArrayIn))
                {
                    img.SetSource(stream);
                }
            }

            return img;
        }

        private void handleOwnInformation(FLAPSocket packet)
        { //{Kuzco} As of Version 0.3.0.0, I see no point in filtering data out of this packet.
            //{Kuzco} Update: Version 0.4.5.0 should have this data filtered.

        }

        private void handleRateChange(FLAPSocket packet)
        {
            int Packet_Offset = 10, POffset = 0;

            double MsgCode = Tools.GetWord(packet.Data, Packet_Offset);

            Packet_Offset += 2;

            int rateClassID = Tools.GetWord(packet.Data, Packet_Offset);

            switch (rateClassID)
            {
                case 1:
                    //Packet Rate
                    POffset = Packet_Offset;
                    m_WindowSize = Tools.GetDword(packet.Data, POffset);
                    POffset += 4;
                    m_ClearLevel = Tools.GetDword(packet.Data, POffset);
                    POffset += 4;
                    m_AlertLevel = Tools.GetDword(packet.Data, POffset);
                    POffset += 4;
                    m_LimitLevel = Tools.GetDword(packet.Data, POffset);
                    POffset += 4;
                    m_DisconnectLevel = Tools.GetDword(packet.Data, POffset);
                    POffset += 4;
                    m_CurrentLevel = Tools.GetDword(packet.Data, POffset);
                    POffset += 4;
                    m_MaxLevel = Tools.GetDword(packet.Data, POffset);
                    POffset += 4;
                    m_LastTime = Tools.GetDword(packet.Data, POffset);
                    POffset += 4;
                    m_CurrentStatus = packet.Data[POffset];
                    POffset++;
                    break;
            }

            double WindowSize = Tools.GetDword(packet.Data, Packet_Offset);
            Packet_Offset += 4;
            double ClearLevel = Tools.GetDword(packet.Data, Packet_Offset);
            Packet_Offset += 4;
            double AlertLevel = Tools.GetDword(packet.Data, Packet_Offset);
            Packet_Offset += 4;
            double LimitLevel = Tools.GetDword(packet.Data, Packet_Offset);
            Packet_Offset += 4;
            double DisconnectLevel = Tools.GetDword(packet.Data, Packet_Offset);
            Packet_Offset += 4;
            double CurrentLevel = Tools.GetDword(packet.Data, Packet_Offset);
            Packet_Offset += 4;
            double MaxLevel = Tools.GetDword(packet.Data, Packet_Offset);
            Packet_Offset += 4;
            double LastTime = Tools.GetDword(packet.Data, Packet_Offset);
            Packet_Offset += 4;
            double curStatus = packet.Data[Packet_Offset];
            Packet_Offset++;

            if (BuddyRateClass != null)
                BuddyRateClass(MsgCode, rateClassID, WindowSize, ClearLevel, AlertLevel, LimitLevel, DisconnectLevel, CurrentLevel, MaxLevel, LastTime, curStatus);
        }

        private void handleRateClasses(FLAPSocket packet)
        {
            int Packet_Offset = 10, m_Count = 0, POffset = 0;

            int rateNumClasses = Tools.GetWord(packet.Data, Packet_Offset);

            Packet_Offset += 2;

            while (m_Count < rateNumClasses)
            {
                int rateClassID = Tools.GetWord(packet.Data, Packet_Offset);

                Packet_Offset += 2;

                switch (rateClassID)
                {
                    case 1:
                        POffset = Packet_Offset;
                        m_WindowSize = Tools.GetDword(packet.Data, POffset);
                        POffset += 4;
                        m_ClearLevel = Tools.GetDword(packet.Data, POffset);
                        POffset += 4;
                        m_AlertLevel = Tools.GetDword(packet.Data, POffset);
                        POffset += 4;
                        m_LimitLevel = Tools.GetDword(packet.Data, POffset);
                        POffset += 4;
                        m_DisconnectLevel = Tools.GetDword(packet.Data, POffset);
                        POffset += 4;
                        m_CurrentLevel = Tools.GetDword(packet.Data, POffset);
                        POffset += 4;
                        m_MaxLevel = Tools.GetDword(packet.Data, POffset);
                        POffset += 4;
                        m_LastTime = Tools.GetDword(packet.Data, POffset);
                        POffset += 4;
                        m_CurrentStatus = packet.Data[POffset];
                        POffset++;
                        break;
                }

                double WindowSize = Tools.GetDword(packet.Data, Packet_Offset);
                Packet_Offset += 4;
                double ClearLevel = Tools.GetDword(packet.Data, Packet_Offset);
                Packet_Offset += 4;
                double AlertLevel = Tools.GetDword(packet.Data, Packet_Offset);
                Packet_Offset += 4;
                double LimitLevel = Tools.GetDword(packet.Data, Packet_Offset);
                Packet_Offset += 4;
                double DisconnectLevel = Tools.GetDword(packet.Data, Packet_Offset);
                Packet_Offset += 4;
                double CurrentLevel = Tools.GetDword(packet.Data, Packet_Offset);
                Packet_Offset += 4;
                double MaxLevel = Tools.GetDword(packet.Data, Packet_Offset);
                Packet_Offset += 4;
                double LastTime = Tools.GetDword(packet.Data, Packet_Offset);
                Packet_Offset += 4;
                double curStatus = packet.Data[Packet_Offset];
                Packet_Offset++;

                m_Count++;

                if (BuddyRateClass != null)
                    BuddyRateClass(4, rateClassID, WindowSize, ClearLevel, AlertLevel, LimitLevel, DisconnectLevel, CurrentLevel, MaxLevel, LastTime, curStatus);
            }
        }

        private void handleRedirection(FLAPSocket packet)
        {
            int Packet_Offset = 5;

            ushort Family = 0, Version = 0;

            string Host = "";

            byte[] Cookie = null;

            int redirectID = Tools.GetWord(packet.Data, Packet_Offset);

            Packet_Offset += 5;

            if (packet.Data[Packet_Offset] == 0 && packet.Data[Packet_Offset + 1] == 6)
                Packet_Offset += 2;

            while (Packet_Offset < packet.Data.Length)
            {
                ushort Type = Tools.GetWord(packet.Data, Packet_Offset);

                Packet_Offset += 2;

                ushort Length = Tools.GetWord(packet.Data, Packet_Offset);

                Packet_Offset += 2;

                switch (Type)
                {
                    case 1:
                        Version = Tools.GetWord(packet.Data, Packet_Offset);
                        break;
                    case 5:
                        Host = Encoding.UTF8.GetString(packet.Data, Packet_Offset, Length);
                        break;
                    case 6:
                        Cookie = new byte[Length];
                        Buffer.BlockCopy(packet.Data, Packet_Offset, Cookie, 0, Length);
                        break;
                    case 13:
                        Family = Tools.GetWord(packet.Data, Packet_Offset);
                        break;
                }

                Packet_Offset += Length;
            }

            if (redirectID == 92)
            {
                m_BART = new Sock();

                m_BART.OnConnect += new Sock.Connected(m_BART_OnConnect);

                m_BART.DataArrival += new Sock.OnArrival(m_BART_DataArrival);

                m_BART_Cookie = Cookie;

                m_BART.beginConnect(Host, m_Port);
            }
        }
        /*End Socket Handling Functions*/

        /*Timers*/
        private void Keep_Alive(Sock m_Sock, object state)
        {
            if (m_Sock == null || m_Sock.State == 0)
                return;

            Send_BOS_Packet(m_Packets.FlapAlive());
        }
        /*End Timers*/

        /*BART Packet Send Queue*/
        private Queue<FLAPSocket> BART_Packets = new Queue<FLAPSocket>();

        private Timer m_BART_Queue;

        private void Send_BART_Packet(FLAPSocket packet)
        {
            BART_Packets.Enqueue(packet);

            if (m_BART_Queue == null)
            {
                m_BART_Queue = new Timer(new TimerCallback(m_BART_Callback));
                m_BART_Queue.Change(5, 5);
            }
        }

        private void m_BART_Callback(object state)
        {
            if (m_BART == null || m_BART.State == 0 || m_BART_Connected == false)
                return;

            if (BART_Packets.Count > 0)
            {
                m_BART.SendData(BART_Packets.Dequeue());
            }
        }
        /*End BART Packet Send Queue*/

        /*BOS Packet Send Queue*/
        private Queue<FLAPSocket> Packets = new Queue<FLAPSocket>();

        private int WaitSize = 0;

        private bool m_SendPackets = true;

        private double m_WindowSize, m_ClearLevel, m_AlertLevel, m_LimitLevel, m_DisconnectLevel, m_CurrentLevel, m_MaxLevel, m_LastTime, m_CurrentStatus;

        private Timer m_Queue;

        private void Send_BOS_Packet(FLAPSocket packet)
        {
            if (m_BOS == null || m_BOS.State == 0 || m_SendPackets == false)
                return;

            if (m_CurrentLevel > m_AlertLevel)
            { //No Pause -- {Kuzco} Send and return quickly.
                Packets.Enqueue(packet);

                WaitSize = 0;

                if (m_Queue == null)
                {
                    m_Queue = new Timer(new TimerCallback(m_Queue_Callback));
                    m_Queue.Change(5, 5);
                }
            }
            else if (m_CurrentLevel < m_AlertLevel && m_CurrentLevel > m_LimitLevel)
            { //Slightly delay each packet.
                Packets.Enqueue(packet);

                WaitSize = 1;

                if (m_Queue == null)
                {
                    m_Queue = new Timer(new TimerCallback(m_Queue_Callback));
                    m_Queue.Change(1500, 1500);
                }
            }
            else if (m_CurrentLevel <= m_LimitLevel)
            { //Delay each packet.
                Packets.Enqueue(packet);

                WaitSize = 2;

                if (m_Queue == null)
                {
                    m_Queue = new Timer(new TimerCallback(m_Queue_Callback));
                    m_Queue.Change(3500, 3500);
                }
            }
        }

        private void m_Queue_Callback(object state)
        {
            if (m_BOS == null || m_BOS.State == 0)
                return;

            if (Packets.Count > 0)
            {
                m_BOS.SendData(Packets.Dequeue());
            }

            switch (WaitSize)
            {
                case 0:
                    m_Queue.Change(5, 5);
                    break;
                case 1:
                    m_Queue.Change(1500, 1500);
                    break;
                case 2:
                    m_Queue.Change(3500, 3500);
                    break;
            }
        }
        /*End BOS Packet Send Queue*/
    }
}
